import socket
import os
import shutil
from pathlib import Path
from typing import final
import ipaddress
import threading
from queue import Queue

@final
class Settings:
    host = '0.0.0.0'
    port_com = 54687
    port_trans = 54688

def split_file(path: Path, size: float) -> str:
    """
    将指定文件按大小分割成多个二进制块

    参数:
        path (str): 源文件路径
        size (float): 每个分割文件的最大大小(Mb)

    返回:
        str: 包含分割文件的临时文件夹路径

    异常:
        FileNotFoundError: 当源文件不存在时抛出
        ValueError: 当文件大小或分割参数不合法时抛出
    """
    # 转换size为字节
    chunk_size = int(size * 1024 * 1024)

    # 验证源文件
    source_path = path
    if not source_path.exists() or not source_path.is_file():
        raise FileNotFoundError(f"源文件不存在: {path}")

    if chunk_size <= 0:
        raise ValueError("分割大小必须为正数")

    # 创建临时文件夹
    temp_dir = Path(f"./{source_path.stem}_parts_{os.getpid()}")
    temp_dir.mkdir(exist_ok=False)

    try:
        # 分割文件
        part_num = 0
        with open(source_path, 'rb') as src_file:
            while True:
                data = src_file.read(chunk_size)
                if not data:
                    break

                part_num += 1
                part_path = temp_dir / f"{part_num}.tmp"
                with open(part_path, 'wb') as part_file:
                    part_file.write(data)

        # 返回包含分割文件的临时目录
        return str(temp_dir)

    except Exception as e:
        # 出错时清理临时文件夹
        if temp_dir.exists():
            shutil.rmtree(temp_dir)
        raise e

def merge_files(folder_path: Path, target_name: str) -> None:
    """
    将文件夹中的二进制文件按序号合并成一个完整文件

    参数:
        folder_path (str): 包含临时文件的文件夹路径
        target_name (str): 目标合并后的文件名

    异常:
        FileNotFoundError: 当文件夹不存在时抛出
        ValueError: 当文件夹中没有可合并的文件时抛出
    """
    folder = folder_path

    # 验证文件夹存在
    if not folder.exists() or not folder.is_dir():
        raise FileNotFoundError(f"文件夹不存在: {folder_path}")

    # 获取所有符合 i.tmp 格式的文件
    temp_files = sorted(
        [f for f in folder.iterdir() if f.name.endswith('.tmp') and f.name[:-4].isdigit()],
        key=lambda x: int(x.name[:-4])  # 按序号排序
    )

    if not temp_files:
        raise ValueError(f"文件夹中没有找到符合条件的 .tmp 文件: {folder_path}")

    # 合并文件
    target_path = Path('.') / target_name
    try:
        with open(target_path, 'wb') as outfile:
            for file in temp_files:
                with open(file, 'rb') as infile:
                    outfile.write(infile.read())
        print(f"成功合并 {len(temp_files)} 个文件到: {target_path}")

    except Exception as e:
        # 出错时删除已部分生成的文件
        if target_path.exists():
            target_path.unlink()
        raise e

# 创建socket服务端
def create_server():
    # 创建通信socket
    communicator = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    communicator.bind((Settings.host, Settings.port_com))
    communicator.listen(1)
    return communicator

# 连接server
def connect_server(ip):
    communicator = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    communicator.connect((ip, Settings.port_com))
    return communicator

# 关闭连接
def close_connection(conn: socket.socket):
    conn.close()

# 发送文件
def send_file_tcp(host, port, file_path:Path):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((host, port))
        s.listen(1)
        print(f"等待连接: {host}:{port}")
        conn, addr = s.accept()
        print(f"连接来自: {addr}")

        first_send = True #第一次发送的时候把文件名发送过去
        # 发送文件
        with open(file_path, 'rb') as f:
            while True:
                data = f.read(4096)
                if not data:
                    break
                if first_send:
                    first_send = False
                    conn.sendall(file_path.name.encode())
                    rt = 'no'
                    while True:
                        rt = conn.recv(4096).decode()
                        if rt == 'yes': break
                conn.sendall(data)

        print(f"文件发送完成: {file_path}")

# 接收文件
def receive_file_tcp(host, port, output_path:Path):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((host, port))

        output_path = output_path / Path('temp.tmp')
        first_receive = True # 第一次接收需要保存一下文件名
        file_name = Path()
        with open(output_path, 'wb') as f:
            while True:
                if first_receive:
                    data = s.recv(4096)
                    first_receive = False
                    file_name = Path(data.decode())
                    s.sendall('yes'.encode())   #同步操作
                data = s.recv(4096)
                if not data:
                    break
                f.write(data)
        print(output_path.parent / file_name)
        output_path.rename(output_path.parent / file_name.name)
        print(f"文件接收完成，保存为: {output_path}")

# 删除文件夹
def delete_folder(path:Path):
    shutil.rmtree(path)

def get_local_ip():
    try:
        # 创建一个 UDP 套接字，连接到外部服务器（无需真实通信）
        with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
            # 连接到任意一个外部 IP:端口（如公共 DNS 服务器）
            s.connect(("8.8.8.8", 80))
            # 获取套接字绑定的本地 IP 地址
            local_ip = s.getsockname()[0]
        return local_ip
    except Exception as e:
        print(f"获取局域网 IP 失败: {e}")
        return "127.0.0.1"  # 失败时返回回环地址